use std::{str::FromStr, sync::Arc};

use solana_client::{
    nonblocking::{pubsub_client::PubsubClient, rpc_client::RpcClient},
    rpc_config::{RpcTransactionConfig, RpcTransactionLogsConfig, RpcTransactionLogsFilter},
};
use solana_sdk::{commitment_config::CommitmentConfig, signature::Signature};
use solana_transaction_status::{UiParsedMessage, UiTransactionEncoding, UiTransactionStatusMeta};
use tokio::sync::Mutex;
use tokio_stream::StreamExt;

use crate::solana::{
    coralcube, magiceden,
    util::{fetch_metadata_from_uri, get_metadata, get_rarity},
};

pub async fn prepare_transaction(
    rpc_client: &RpcClient,
    signature: &str,
) -> Option<(UiTransactionStatusMeta, UiParsedMessage)> {
    let signature = Signature::from_str(signature).unwrap();
    let transaction = rpc_client
        .get_transaction_with_config(
            &signature,
            RpcTransactionConfig {
                commitment: Some(CommitmentConfig::confirmed()),
                encoding: Some(UiTransactionEncoding::JsonParsed),
                max_supported_transaction_version: Some(0),
            },
        )
        .await;

    let transaction = match transaction {
        Ok(tranaction) => tranaction,
        Err(_) => return None,
    };

    let meta = match transaction.transaction.meta {
        Some(meta) => meta,
        None => return None,
    };

    let transaction = match transaction.transaction.transaction {
        solana_transaction_status::EncodedTransaction::LegacyBinary(_) => return None,
        solana_transaction_status::EncodedTransaction::Binary(_, _) => return None,
        solana_transaction_status::EncodedTransaction::Json(json) => json,
    };

    let message = match transaction.message {
        solana_transaction_status::UiMessage::Parsed(message) => message,
        solana_transaction_status::UiMessage::Raw(_) => return None,
    };

    Some((meta, message))
}

pub async fn start_sniper(
    tx: Arc<Mutex<tokio::sync::watch::Sender<serde_json::Value>>>,
    mention: &str,
) -> eyre::Result<()> {
    let rpc_client = RpcClient::new("https://ssc-dao.genesysgo.net/".to_string());

    let pubsub_client = PubsubClient::new("wss://ssc-dao.genesysgo.net/")
        .await
        .unwrap();

    let (mut logs_, _logs_unsubscribe) = pubsub_client
        .logs_subscribe(
            RpcTransactionLogsFilter::Mentions(vec![mention.to_string()]),
            RpcTransactionLogsConfig {
                commitment: Some(CommitmentConfig::processed()),
            },
        )
        .await?;

    while let Some(log) = logs_.next().await {
        let mut socket_data: Option<serde_json::Value> = None;

        let signature = log.value.signature;

        let (meta, message) = match prepare_transaction(&rpc_client, &signature).await {
            Some(data) => data,
            None => continue,
        };

        let is_nft_sale = meta
            .log_messages
            .as_ref()
            .unwrap()
            .iter()
            .any(|log| log.contains("Instruction: Sell"));

        if is_nft_sale {
            if mention == "hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk" {
                socket_data =
                    coralcube::process_coralcube(&rpc_client, &signature, &message, &meta).await;
            }

            if mention == "M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K" {
                socket_data =
                    magiceden::process_magiceden(&rpc_client, &signature, &message, &meta).await;
            }
        }

        if socket_data.is_some() {
            let mut socket_data = socket_data.unwrap();
            let tx = tx.lock().await;

            let mint = socket_data["mint"].as_str().unwrap().to_string();

            let metadata = get_metadata(&mint).await.unwrap();
            socket_data["metadata"] = fetch_metadata_from_uri(metadata.data.uri).await.unwrap();
            socket_data["rarity"] =
                get_rarity(&mint, crate::solana::formula::RarityFormula::CoralCube)
                    .await
                    .unwrap();

            match tx.send(socket_data) {
                Ok(_) => (),
                Err(err) => println!("ХУЙНЯ КАКАЯ-ТО СООБЩЕНИЕ НЕ ОТПРАВЛЕНО {:?}", err),
            }
        }
    }

    Ok(())
}
